import threading, Queue, random, thread,time
from math import *
import Image
import ImageTk
import Tkinter

# TODO Need to place walls and overlay sensors
class graphics_engine(object):
	def __init__(self,height=480,width=640):
		self.tk_root=None
		self.main_canvas=None
		self.height=height
		self.width=width
		self.action_queue=Queue.Queue()
		self.mainloop_thread=threading.Thread(target=self.__event_loop_wrap)
		self.agent_graphics_id={} # This is the graphics handle that uniquely identified the agent
		self.text_graphics_id={}
		self.root_created=thread.allocate_lock() # This is to ensure that root is created before start event loop returns
		self.object_im = {}
		self.previous_headings = {}
		self.previous_states = {}
		self.image_list = {}
	def start_event_loop(self):
		"""This creates the graphics thread, and only exists when tk_root is valid"""
		self.root_created.acquire()
		self.mainloop_thread.start()
		self.root_created.acquire()
		self.root_created.release()
	def __action_loop(self,**args):
		delay_time=10
		lots_of_commands=2
		if self.action_queue.qsize() >= lots_of_commands: # If we are behind in processing, speed it up
			delay_time=1
		try:
			cur_action=self.action_queue.get(block=False)
			cur_action()
		except Queue.Empty:
			pass
		self.tk_root.after(delay_time,self.__action_loop)
	def __event_loop_wrap(self):
		import Tkinter
		self.tk_root=Tkinter.Tk()
		# Custom Event Bindings (these don't change)
		#self.tk_root.bind('<<stop_event_loop>>',self.__stop_event_loop)
		# Create canvas and run loop
		self.main_canvas=Tkinter.Canvas(self.tk_root,width=self.width,height=self.height, background = 'black')
		self.main_canvas.pack()
		self.__action_loop()
		self.tk_root.bind('q',lambda x: self.__stop_event_loop())
		self.root_created.release()
		self.tk_root.mainloop()
	# The following functions are thread safe event callers
	# These rebind the functions with the provided arguments, and then throw the event to allow passing arguments to events (this is a great hack BTW, lambdas FTW)
	# The naming convention for the wrapper (event caller) is NAME, the event function's name is __NAME, and the event is <<NAME>>
	def update_pos(self,agent_num,pos,heading,graphics_state,im_filename):
		"""This will update the position and heading of an agent graphically, if it doesn't exist it will be created"""
		self.action_queue.put(lambda : self.__update_pos(agent_num,pos,heading,graphics_state,im_filename))
	def update_text(self, text_num, pos, text):
		self.action_queue.put(lambda : self.__update_text(text_num,pos,text))
	def create_walls(self,walls,width):
		"""This will graphically create a wall from the provided list specified by 2 points located in a tuple in the format (x_0,y_0,x_1,y_1)"""
		self.action_queue.put(lambda : self.__create_walls(walls,width))
	def set_background(self,bg_img):
		self.action_queue.put(lambda : self.__set_background(bg_img))
	def remove_agent(self,agent_num):
		"""This will remove the agent from the graphics list if it exists"""
		self.action_queue.put(lambda : self.__remove_agent(agent_num))
	def remove_text(self, text_num):
		self.action_queue.put(lambda : self.__remove_text(text_num))
	def stop_event_loop(self):
		"""This will stop the event loop from running and join in the thread"""
		self.tk_root.event_generate('<<stop_event_loop>>',when='tail')
		self.mainloop_thread.join() # This removes the extra thread after the mainloop quits
	# The following functions must be called by custom events (above)
	def __update_pos(self,agent_num,pos,heading,graphics_state,im_filename):
		if self.agent_graphics_id.has_key(agent_num): # If it already exists
			if im_filename != 'test_agent_1' and im_filename != 'test_agent_2' and im_filename != 'test_ball' and im_filename != 'bullet':
				if heading != self.previous_headings[agent_num] or graphics_state[-1] != self.previous_states[agent_num]:
					self.main_canvas.itemconfig(self.agent_graphics_id[agent_num], image=self.make_image(agent_num, heading, graphics_state))
					self.previous_headings[agent_num] = heading
					self.previous_states[agent_num] = graphics_state[-1]
				self.main_canvas.coords(self.agent_graphics_id[agent_num],pos[0], pos[1])
			elif im_filename == 'test_ball':
				self.main_canvas.coords(self.agent_graphics_id[agent_num],pos[0]-9, pos[1]-9, pos[0]+9, pos[1]+9)
			elif im_filename == 'bullet':
				self.main_canvas.coords(self.agent_graphics_id[agent_num],pos[0]-5, pos[1]-5, pos[0]+5, pos[1]+5)
			else:
				self.main_canvas.coords(self.agent_graphics_id[agent_num],pos[0]-22, pos[1]-22, pos[0]+22, pos[1]+22)		
		else: # If it doesn't exist 
			if im_filename != 'test_agent_1' and im_filename != 'test_agent_2' and im_filename != 'test_ball' and im_filename != 'bullet':
				self.previous_headings[agent_num] = heading
				self.previous_states[agent_num] = graphics_state[-1]
				self.load_image(agent_num, im_filename)
				self.agent_graphics_id[agent_num]=self.main_canvas.create_image([pos[0], pos[1]], image=self.make_image(agent_num, heading, graphics_state))
			elif im_filename == 'test_agent_1':
				self.agent_graphics_id[agent_num]=self.main_canvas.create_oval([pos[0]-22, pos[1]-22, pos[0]+22, pos[1]+22], outline='yellow', width=2)
			elif im_filename == 'test_agent_2':
				self.agent_graphics_id[agent_num]=self.main_canvas.create_oval([pos[0]-22, pos[1]-22, pos[0]+22, pos[1]+22], outline='blue', width=2)
			elif im_filename == 'bullet':
				self.agent_graphics_id[agent_num]=self.main_canvas.create_oval([pos[0]-5, pos[1]-5, pos[0]+5, pos[1]+5], outline='black', width=2)
			else:
				self.agent_graphics_id[agent_num]=self.main_canvas.create_oval([pos[0]-9, pos[1]-9, pos[0]+9, pos[1]+9], outline='orange', width=2)
	def __remove_agent(self,agent_num):
		if self.agent_graphics_id.has_key(agent_num): # If it exists
			self.main_canvas.delete(self.agent_graphics_id[agent_num])
			del self.agent_graphics_id[agent_num]
	def __create_walls(self,walls,width):
		map(lambda x: self.main_canvas.create_line(x,width=width, fill='black'),walls)
	def __set_background(self,bg_img=None):
		if bg_img:
			im = ImageTk.PhotoImage(Image.open(bg_img))
			self.background=im
			self.main_canvas.create_image([360,360], image = im)
	def __stop_event_loop(self):
		"""This stops the mainloop, it must be called FROM mainloop, thus can be triggered by an event"""
		self.tk_root.destroy()
	# These functions provide commonly used minor functionality
	def random_pos(self):
		return [self.width*random.random(),self.height*random.random()]
	# Creates an image from the agent's main image that reflects the agent's rotation.
	def load_image(self, agent_num, im_filename):
		im_1_file = im_filename + '.png'
		im = Image.open(im_1_file)
		im_1 = im.convert('RGBA')
		im_1_file = im_filename + 'o.png'
		im = Image.open(im_1_file)
		im_2 = im.convert('RGBA')
		self.object_im[agent_num] = [im_1,im_2]
	def make_image(self, agent_num, heading, graphics_state):
		im = ImageTk.PhotoImage(self.object_im[agent_num][graphics_state[-1]].rotate(heading*-180/pi))
		self.image_list[agent_num] = im
		return im
	
	#Defines functions for use in displaying text
	def __update_text(self,text_num,pos,text):
		if self.text_graphics_id.has_key(text_num): # If it already exists
			self.main_canvas.delete(self.text_graphics_id[text_num])
		self.text_graphics_id[text_num]=self.main_canvas.create_text([pos[0], pos[1]], fill='white', text=text, font=('Courier', '14'))
	def __remove_text(self,text_num):
		if self.text_graphics_id.has_key(text_num): # If it exists
			self.main_canvas.delete(self.text_graphics_id[text_num])
			del self.text_graphics_id[text_num]